package top.infopub.zookeeper.confclient.config;


import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;

import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.StrLookup;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.ZKPaths;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import top.infopub.exception.ServiceException;
import top.infopub.util.SpringContext;
import top.infopub.zookeeper.ZookeeperConst;
import top.infopub.zookeeper.confclient.ContextUtils;
import top.infopub.zookeeper.confclient.listener.ConfigurationListener;


/**
 * PropertiesConfiguration
 * @author Awoke
 * @version 2018年9月14日
 * @see PropertiesConfiguration
 * @since
 */
public class PropertiesConfiguration {

    /**
     * 各project的conf-node
     */
    private static String PATH_ZOOKEEPER_CONIF = ZookeeperConst.CONFIG_PATH
                                                 + ContextUtils.PROJECT_NAME;

    /**
     * dataChangeNotice
     */
    private static String PATH_ZOOKEEPER_BUS_CONIF = ZookeeperConst.CONFIG_PATH
                                                     + "dataChangeNotice";

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private StrSubstitutor substitutor;

    private Properties store = new Properties();

    private CuratorFramework zkClient;

    private PathChildrenCache pathChildrenCache;

    private PathChildrenCache pathChildrenCache_NOTICE;

    // 增加自定义监听器 map
    // K-V = nodeName-serviceId  key为zookeeper节点的名，value为服务类注册到spring中的id 程序中会根据id去找实现类
    private Map<String, String> listenerNotice;

    private final ConnectionStateListener connectionStateListener = new ConnectionStateListener() {
        @Override
        public void stateChanged(CuratorFramework client, ConnectionState newState) {
            if ((newState == ConnectionState.RECONNECTED)
                || (newState == ConnectionState.CONNECTED)) {
                try {
                    logger.debug("重新注册监听");
                    creatCacheAddWatcher();
                    addListerForBusinessConf();
                }
                catch (Exception e) {
                    logger.error("重新注册监听", e);
                }
            }
        }
    };

    /**
     * 监听注入节点的配置  dataChangeNotice
     */
    private void addListerForBusinessConf() {
        if (listenerNotice == null || listenerNotice.isEmpty()) {
            return;
        }
        pathChildrenCache_NOTICE = new PathChildrenCache(zkClient, PATH_ZOOKEEPER_BUS_CONIF, true);
        try {
            pathChildrenCache_NOTICE.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
        }
        catch (Exception e) {
            logger.error("Zookeeper不存创建Node Caceh对于" + PATH_ZOOKEEPER_BUS_CONIF, e);
        }
        PathChildrenCacheListener listener = new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
                throws Exception {
                PathChildrenCacheEvent.Type eventType = event.getType();
                switch (eventType) {
                    case CHILD_ADDED:
                    case CHILD_UPDATED:
                        // event.getData().getPath()
                        if (listenerNotice != null) {
                            String nodeName = ZKPaths.getNodeFromPath(event.getData().getPath());
                            String serviceId = listenerNotice.get(nodeName);
                            if (StringUtils.isBlank(serviceId)) {
                                return;
                            }
                            ConfigurationListener customerListener = SpringContext.getBean(serviceId);
                            if (customerListener != null) {
                                logger.info(nodeName + " 通知变更,对应服务:" + serviceId);
                                customerListener.configurationChangedNotice(nodeName, new String(
                                    event.getData().getData()));
                                logger.info(nodeName + " 通知变更完成,对应服务:" + serviceId);
                            }
                        }
                        break;
                    default:
                        if (event.getData() != null)
                            logger.info("PathChildrenCache changed : {path:"
                                        + event.getData().getPath() + " data:"
                                        + new String(event.getData().getData()) + "}");
                }
            }
        };
        pathChildrenCache_NOTICE.getListenable().addListener(listener);
    }

    /**
     * 为配置节点增加监听事件
     */
    private void creatCacheAddWatcher() {
        pathChildrenCache = new PathChildrenCache(zkClient, PATH_ZOOKEEPER_CONIF, true);
        try {
            pathChildrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
        }
        catch (Exception e) {
            logger.error("Zookeeper不存创建Node Caceh对于" + PATH_ZOOKEEPER_CONIF, e);
        }
        PathChildrenCacheListener listener = new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
                throws Exception {
                PathChildrenCacheEvent.Type eventType = event.getType();
                switch (eventType) {
                    case CHILD_ADDED:
                        saveData(event.getData(), eventType);
                        break;
                    case CHILD_REMOVED:
                        saveData(event.getData(), eventType);
                        break;
                    case CHILD_UPDATED:
                        saveData(event.getData(), eventType);
                        break;
                    default:
                        if (event.getData() != null && event.getData().getData() != null)
                            logger.info("PathChildrenCache changed : {path:"
                                        + event.getData().getPath() + " data:"
                                        + new String(event.getData().getData()) + "}");
                }
            }
        };
        pathChildrenCache.getListenable().addListener(listener);
    }

    /**
     * 初始化设置，并增加接听
     * @param projCode
     * @param profile
     */
    public PropertiesConfiguration(CuratorFramework zkClient) {
        this.zkClient = zkClient;
        substitutor = new StrSubstitutor(createInterpolator());
        this.initConfig();
        zkClient.getConnectionStateListenable().addListener(connectionStateListener);
    }

    /**
     * 初始化设置，并增加接听
     * @param projCode
     * @param profile
     */
    public PropertiesConfiguration(CuratorFramework zkClient, Map<String, String> listenerNotice) {
        this(zkClient);
        this.listenerNotice = listenerNotice;
        addListerForBusinessConf();
    }

    /**
     * 读取zookeeper的配置信息，并保存到磁盘
     */
    protected void initConfig() {
        try {
            creatCacheAddWatcher();
            List<ChildData> datas = pathChildrenCache.getCurrentData();
            for (ChildData data : datas) {
                saveData(data);
            }
            FileUtils.saveData(ContextUtils.PROJECT_NAME, this.store);
        }
        catch (Exception e) {
            logger.error("读取系统ZK配置信息失败--系统将会读取本地配置文件", e);
        }
        try {
            if (store.isEmpty()){
                this.store = FileUtils.readConfigFromLocal(ContextUtils.PROJECT_NAME);
            }
        }
        catch (IOException e) {
            throw new ServiceException(e);
        }
    }

    public void saveData(ChildData childData)
        throws ServiceException {
        byte[] value = childData.getData();
        store.put(ZKPaths.getNodeFromPath(childData.getPath()), value == null ? "" : new String(
            value));
    }

    /**
     * 处理节点事件的变更
     * @param childData
     * @param eventType
     * @throws ServiceException
     */
    public void saveData(ChildData childData, Type eventType)
        throws ServiceException {
        switch (eventType) {
            case CHILD_ADDED:
            case CHILD_UPDATED:
                saveData(childData);
                FileUtils.saveData(ContextUtils.PROJECT_NAME, this.store);
                break;
            case CHILD_REMOVED:
                store.remove(ZKPaths.getNodeFromPath(childData.getPath()));
                FileUtils.saveData(ContextUtils.PROJECT_NAME, this.store);
                break;
            default:
                break;
        }
    }

    private String getProperty(String key) {
        return store.getProperty(key);
    }

    public Properties getProperties() {
        return this.store;
    }

    public boolean getBoolean(String key) {
        Boolean b = getBoolean(key, null);
        if (b != null) {
            return b.booleanValue();
        }
        else {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
    }

    public boolean getBoolean(String key, boolean defaultValue) {
        return getBoolean(key, new Boolean(defaultValue)).booleanValue();
    }

    public Boolean getBoolean(String key, Boolean defaultValue) {
        String value = getProperty(key);
        if (value == null) {
            return defaultValue;
        }
        else {
            try {
                return PropertyConverter.toBoolean(interpolate(value));
            }
            catch (ConversionException e) {
                throw new ConversionException('\'' + key + "' doesn't map to a Boolean object", e);
            }
        }
    }

    public byte getByte(String key) {
        Byte b = getByte(key, null);
        if (b != null) {
            return b.byteValue();
        }
        else {
            throw new NoSuchElementException('\'' + key + " doesn't map to an existing object");
        }
    }

    public byte getByte(String key, byte defaultValue) {
        return getByte(key, new Byte(defaultValue)).byteValue();
    }

    public Byte getByte(String key, Byte defaultValue) {
        String value = getProperty(key);

        if (value == null) {
            return defaultValue;
        }
        else {
            try {
                return PropertyConverter.toByte(interpolate(value));
            }
            catch (ConversionException e) {
                throw new ConversionException('\'' + key + "' doesn't map to a Byte object", e);
            }
        }
    }

    public double getDouble(String key) {
        Double d = getDouble(key, null);
        if (d != null) {
            return d.doubleValue();
        }
        else {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
    }

    public double getDouble(String key, double defaultValue) {
        return getDouble(key, new Double(defaultValue)).doubleValue();
    }

    public Double getDouble(String key, Double defaultValue) {
        String value = getProperty(key);
        if (value == null) {
            return defaultValue;
        }
        else {
            try {
                return PropertyConverter.toDouble(interpolate(value));
            }
            catch (ConversionException e) {
                throw new ConversionException('\'' + key + "' doesn't map to a Double object", e);
            }
        }
    }

    public float getFloat(String key) {
        Float f = getFloat(key, null);
        if (f != null) {
            return f.floatValue();
        }
        else {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
    }

    public float getFloat(String key, float defaultValue) {
        return getFloat(key, new Float(defaultValue)).floatValue();
    }

    public Float getFloat(String key, Float defaultValue) {
        String value = getProperty(key);
        if (value == null) {
            return defaultValue;
        }
        else {
            try {
                return PropertyConverter.toFloat(interpolate(value));
            }
            catch (ConversionException e) {
                throw new ConversionException('\'' + key + "' doesn't map to a Float object", e);
            }
        }
    }

    public int getInt(String key) {
        Integer i = getInteger(key, null);
        if (i != null) {
            return i.intValue();
        }
        else {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
    }

    public int getInt(String key, int defaultValue) {
        Integer i = getInteger(key, null);
        if (i == null) {
            return defaultValue;
        }
        return i.intValue();
    }

    public Integer getInteger(String key, Integer defaultValue) {
        String value = getProperty(key);
        if (value == null) {
            return defaultValue;
        }
        else {
            try {
                return PropertyConverter.toInteger(interpolate(value));
            }
            catch (ConversionException e) {
                throw new ConversionException('\'' + key + "' doesn't map to an Integer object", e);
            }
        }
    }

    public long getLong(String key) {
        Long l = getLong(key, null);
        if (l != null) {
            return l.longValue();
        }
        else {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
    }

    public long getLong(String key, long defaultValue) {
        return getLong(key, new Long(defaultValue)).longValue();
    }

    public Long getLong(String key, Long defaultValue) {
        String value = getProperty(key);
        if (value == null) {
            return defaultValue;
        }
        else {
            try {
                return PropertyConverter.toLong(interpolate(value));
            }
            catch (ConversionException e) {
                throw new ConversionException('\'' + key + "' doesn't map to a Long object", e);
            }
        }
    }

    public short getShort(String key) {
        Short s = getShort(key, null);
        if (s != null) {
            return s.shortValue();
        }
        else {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
    }

    public short getShort(String key, short defaultValue) {
        return getShort(key, new Short(defaultValue)).shortValue();
    }

    public Short getShort(String key, Short defaultValue) {
        String value = getProperty(key);
        if (value == null) {
            return defaultValue;
        }
        else {
            try {
                return PropertyConverter.toShort(interpolate(value));
            }
            catch (ConversionException e) {
                throw new ConversionException('\'' + key + "' doesn't map to a Short object", e);
            }
        }
    }

    public String getString(String key) {
        String s = getString(key, null);
        if (s != null) {
            return s;
        }
        else {
            return null;
        }
    }

    public String getString(String key, String defaultValue) {
        String value = getProperty(key);
        if (value instanceof String) {
            return interpolate((String)value);
        }
        else {
            return interpolate(defaultValue);
        }
    }

    protected String interpolate(String value) {
        Object result = substitutor.replace(value);
        return (result == null) ? null : result.toString();
    }

    @SuppressWarnings("rawtypes")
    protected ConfigurationInterpolator createInterpolator() {
        ConfigurationInterpolator interpol = new ConfigurationInterpolator();
        interpol.setDefaultLookup(new StrLookup() {
            @Override
            public String lookup(String var) {
                String prop = getProperty(var);
                return (prop != null) ? prop : null;
            }
        });
        return interpol;
    }

    /**
     * Test
     * @param args 
     * @see
     */
    public static void main(String[] args) {
        CuratorFramework client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181").sessionTimeoutMs(
            5000).connectionTimeoutMs(3000).retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
        client.start();
        //PropertiesConfiguration c = new PropertiesConfiguration(client);
        try {
            Thread.sleep(20000l);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
